package jroguelike.main;

import aimozg.joglfw.GLColor;
import jroguelike.main.items.MeleeWeapon;
import jroguelike.main.proto.CreatureProtos;
import jroguelike.main.proto.ItemProtos;
import jroguelike.utils.ColouredString;
import jroguelike.utils.ColouredText;
import jroguelike.utils.Dice;

import java.util.*;
import java.util.concurrent.LinkedBlockingDeque;

import static jroguelike.utils.Dice.StandardDie.d100;

/**
 * Created by IntelliJ IDEA.
 * User: aimozg
 * Date: 19.07.11
 * Time: 20:40
 */
public class World {
	public GLColor DEFAULT_LOG_FG = GLColor.LIGHTGRAY;

	public static final int ACTION_COST = 1000;
	public static final int ACTION_PICKUP_COST = 1000;
	public static final int ACTION_WIELD_COST = 500;
	public static final int ACTION_WEAR_COST = 2000;
	public static final int ACTION_SWAP_COST = 500;
	public static final int ACTION_DROP_COST = 300;

	public static final int GT_MS_PER_STEP = 250;

	private Creature player;
	public Gametime gametime = new Gametime(0);
	public final Deque<ColouredText> log = new LinkedBlockingDeque<>();

	private CreatureProtos creaturesProtos = new CreatureProtos(this);
	private ItemProtos itemProtos = new ItemProtos(this);

	private final List<Creature> creatures = new LinkedList<>();
	public RLMap map1;
	public final Random rng = new Random();

	public Creature player() {
		return player;
	}

	public static World createDefaultWorld() {
		World w = new World();
		w.initDefault();
		return w;
	}

	public void initDefault() {
		addLog(new ColouredString(GLColor.RED, "Здравствуй, "), new ColouredString(GLColor.GREEN, "мир"));

		map1 = new RLMap(20, 20);
		map1.name = "Пустоши";
		for (int i = 0; i < 10; i++) {
			map1.tile(rng.nextInt(map1.height * map1.width), Terrain.WALL);
		}
		int rx = rng.nextInt(map1.width - 8) + 4;
		int ry = rng.nextInt(map1.height - 8) + 4;
		for (int i = 0; i < 4; i++) {
			map1.tile(rx + i, ry, Terrain.WALL);
			map1.tile(rx + i, ry + 3, Terrain.WALL);
			map1.tile(rx, ry + i, Terrain.WALL);
			map1.tile(rx + 3, ry + i, Terrain.WALL);
		}
		map1.tile(rx + 1, ry, Terrain.DOOR_C);

		Creature pc = new Creature(creaturesProtos.player);
		setPc(pc);
		creatures.add(pc);
		map1.tile(10, 10, Terrain.FLOOR);
		pc.moveTo(map1, 10, 10);

		Creature[] mobs = {
				new Creature(creaturesProtos.feralGhoul),
				new Creature(creaturesProtos.rat),
				new Creature(creaturesProtos.wanderer)
		};
		for (Creature cr : mobs) {
			creatures.add(cr);
			int gx, gy;
			do {
				gx = rng.nextInt(map1.width - 2) + 1;
				gy = rng.nextInt(map1.height - 2) + 1;
			} while (!map1.tile(gx, gy).passable || map1.creature(gx, gy) != null);
			cr.moveTo(map1, gx, gy);
		}

		placeRandomly(itemProtos.aAdvancedPowerArmor.makeClone());
		placeRandomly(itemProtos.aVault13Jumpsuit.makeClone());
		placeRandomly(itemProtos.uHealingPowder.makeClone());
		placeRandomly(itemProtos.uStimpack.makeClone());
		placeRandomly(itemProtos.wCombatKnife.makeClone());
		placeRandomly(itemProtos.wKnife.makeClone());
		placeRandomly(itemProtos.x9mmAmmo.makeClone());
		placeRandomly(itemProtos.xVaultWaterChip.makeClone());
	}

	private void placeRandomly(RLItem item) {
		int xy;
		do {
			xy = rng.nextInt(map1.height * map1.width);
		} while (map1.item(xy) != null || !map1.tile(xy).passable);
		item.moveTo(map1, xy);
	}

	private World() {
		Dice.rng = rng;
	}

	public void addLog(ColouredText string) {
		log.add(string);
	}

	public void addLog(ColouredString... strings) {
		addLog(new ColouredText(strings));
	}

	public void addLog(GLColor fg, GLColor bg, String text) {
		addLog(new ColouredText(new ColouredString(fg, text, bg)));
	}

	public void addLog(GLColor fg, String text) {
		addLog(new ColouredText(new ColouredString(fg, text)));
	}

	public void addLog(String text) {
		addLog(new ColouredText(new ColouredString(DEFAULT_LOG_FG, text)));
	}

	public void playerAction(int dx, int dy) {
		int npx = player.x() + dx;
		int npy = player.y() + dy;
		if (map1.inBounds(npx, npy)) {
			Terrain tile = map1.tile(npx, npy);
			Creature creature = map1.creature(npx, npy);
			if (map1.canMoveTo(npx, npy)) {
				actionStep(player, dx, dy);

			} else {
				if (creature != null) {
					tryActionMeleeAttack(player, creature);
				} else {
					if (tile == Terrain.DOOR_C) {
						tryActionOpen(player, dx, dy);
					} else {
						addLog("You slam into " + tile.name + "!");
					}
				}
			}
		}
	}

	private boolean aistep() {
		Creature actor = null;
		for (Creature creature : creatures) {
			if (creature.canAct()) {
				if (actor == null || actor.actionPoints < creature.actionPoints) {
					actor = creature;
				}
			}
		}
		if (actor != null) {
			if (!actor.isPc()) {
				int ap1 = actor.actionPoints;
				actor.ai.perform();
				if (actor.actionPoints == ap1) {
					tryActionWait(actor);
				}
			}
			return true;
		}
		return false;
	}

	private void timestep() {
		gametime = gametime.add(GT_MS_PER_STEP);
		for (Creature creature : creatures) {
			creature.actionPoints += creature.speed();
		}
	}

	public boolean step() {
		if (!aistep()) {
			timestep();
			return false;
		}
		return true;
	}

	public void setPc(Creature pc) {
		if (player != null) {
			player.notifyIsPc(false);
		}
		pc.notifyIsPc(true);
		player = pc;
	}

	/////////////////////// ACTIONS

	public void tryActionWait(Creature me) {
		actionWait(me);
	}

	public void actionWait(Creature me) {
		me.actionPoints = 0;
		me.addLog("You wait");
	}

	public boolean tryActionStep(Creature me, int dx, int dy) {
		if (me.map() != null && me.map().canMoveTo(me.x() + dx, me.y() + dy)) {
			actionStep(me, dx, dy);
			return true;
		}
		return false;
	}

	public void actionStep(Creature me, int dx, int dy) {
		me.actionPoints -= ACTION_COST;
		me.moveTo(me.x() + dx, me.y() + dy);
	}

	public boolean tryActionOpen(Creature me, int dx, int dy) {
		int tx = me.x() + dx;
		int ty = me.y() + dy;
		if (me.map() != null && me.map().inBounds(tx, ty) && me.map().tile(tx, ty) == Terrain.DOOR_C) {
			actionOpen(me, dx, dy);
			return true;
		}
		me.addLog("Cannot open door");
		return false;
	}

	public void actionOpen(Creature me, int dx, int dy) {
		me.actionPoints -= ACTION_COST;
		me.map().tile(me.x() + dx, me.y() + dy, Terrain.DOOR_O);
		me.addLog("You opened door");
	}

	public boolean tryActionClose(Creature me, int dx, int dy) {
		int tx = me.x() + dx;
		int ty = me.y() + dy;
		if (me.map() != null && me.map().inBounds(tx, ty) && me.map().tile(tx, ty) == Terrain.DOOR_O) {
			actionClose(me, dx, dy);
			return true;
		}
		me.addLog("Cannot close door");
		return false;
	}

	public void actionClose(Creature me, int dx, int dy) {
		me.actionPoints -= ACTION_COST;
		me.map().tile(me.x() + dx, me.y() + dy, Terrain.DOOR_C);
		me.addLog("You closed door");
	}

	public boolean tryActionMeleeAttack(Creature me, Creature other) {
		if (other != null && me.map() == other.map() && me.map().range(me, other) == 1) {
			actionMeleeAttack(me, other);
			return true;
		}
		me.addLog("Cannot attack!");
		return false;
	}

	public boolean tryActionAutoPickup(Creature me) {
		int xy = me.map().xy(me.x(), me.y());
		RLItem item = me.map().item(xy);
		if (item == null) {
			me.addLog("Nothing to pickup!");
			return false;
		}
		// TODO check inventory size
		actionAutoPickup(me, item);
		return true;
	}

	public void actionAutoPickup(Creature me, RLItem item) {
		int xy = item.xy;
		item.moveTo(null, 0);
		if (item.clazz == ItemClass.ARMOR && me.inventory.get(Creature.InventorySlot.ARMOR).isEmpty()) {
			me.inventory.put(Creature.InventorySlot.ARMOR, item);
			me.actionPoints -= ACTION_WEAR_COST;
			me.addLog("You wear %s", item.displayName());//TODO use item color here and in other branches
		} else if (item.clazz == ItemClass.WEAPON && me.inventory.get(Creature.InventorySlot.PRIMARY_WEAPON).isEmpty()) {
			me.inventory.put(Creature.InventorySlot.PRIMARY_WEAPON, item);
			me.actionPoints -= ACTION_WIELD_COST;
			me.addLog("You wield %s", item.displayName());
		} else if (item.clazz == ItemClass.WEAPON && me.inventory.get(Creature.InventorySlot.SECONDARY_WEAPON).isEmpty()) {
			me.inventory.put(Creature.InventorySlot.SECONDARY_WEAPON, item);
			me.actionPoints -= ACTION_WIELD_COST;
			me.addLog("You wield %s", item.displayName());
		} else {
			me.inventory.put(Creature.InventorySlot.BACKPACK, item);
			me.actionPoints -= ACTION_PICKUP_COST;
			me.addLog("You pickup %s", item.displayName());
		}
	}

	public boolean tryActionSwapWeapons(Creature me) {
		me.actionPoints -= ACTION_SWAP_COST;
		me.primaryWeaponActive = !me.primaryWeaponActive;
		me.addLog("You swap your weapons");
		return true;
	}

	public boolean tryActionDrop(Creature me, RLItem item) {
		if (me.inventory.remove(Creature.InventorySlot.BACKPACK, item)) {
			me.actionPoints -= ACTION_DROP_COST;
			me.map().dropItem(item, me.x(), me.y());
			me.addLog("You drop " + item.displayName());
			return true;
		}
		me.addLog("You don't posses such item!");
		return false;
	}

	public void actionMeleeAttack(Creature me, Creature other) {
		me.actionPoints -= ACTION_COST;
		RLItem weapon = me.getWeapon();
		int dmg = me.rollMeleeDamage((weapon != null && weapon instanceof MeleeWeapon) ? (MeleeWeapon) weapon : null);
		other.hitPts -= dmg;
		if (d100(50)) {
			if (d100(40)) {
				me.map().putBlood(other.x(), other.y());
			} else {
				me.map().putBlood(other.x() + rng.nextInt(3) - 1, other.y() + rng.nextInt(3) - 1);
			}
		}
		if (other.hitPts <= 0) {
			me.addLog("You hit %s for %d HP and kill it", other.name, dmg);
			other.addLog(GLColor.RED, "%s hits you for %d HP and kills you!", me.name, dmg);
			notifyCreatureDead(other);
		} else {
			me.addLog("You hit %s for %d HP", other.name, dmg);
			other.addLog("%s hits you for %d HP", me.name, dmg);
		}
	}

	public void notifyCreatureDead(Creature creature) {
		if (creature.isPc()) {
			// TODO gameover
		}
		creature.notifyDead(true);
		if (creature.map() != null) {
			for (Map.Entry<Creature.InventorySlot, RLItem> e : creature.inventory) {
				// TODO check if dropable
				creature.map().dropItem(e.getValue(), creature.x(), creature.y());
			}
		}
		creature.moveTo(null, 0, 0);
		creatures.remove(creature);
	}

	@Override
	public String toString() {
		return "[" + getClass().toString() + "]";
	}

}
